package cn.jasonone.jame;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.jasonone.jame.basic.GameContext;
import cn.jasonone.jame.basic.KeyEventHandler;
import cn.jasonone.jame.basic.MouseEventHandler;
import cn.jasonone.jame.cycle.GameApplicationLifeCycle;
import cn.jasonone.jame.model.DefaultGameContext;
import cn.jasonone.jame.model.Dimension;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;

@Slf4j
public abstract class JameApplication extends Application implements GameApplicationLifeCycle {
    private static final String SIZE = "__SYSTEM_SIZE__";

    /**
     * 存储场景
     */
    private Map<String, GameScene> gameScenes = new LinkedHashMap<>();
    /**
     * 当前激活的场景ID
     */
    private String activeSceneId;

    private GameContext<JameApplication> gameContext = new DefaultGameContext(this, null, null);
    /**
     * 游戏窗口大小
     */
    @Getter
    private Dimension size = new Dimension(800, 600);

    /**
     * 按键事件集合
     */
    private final Map<String, KeyEventHandler> keyEvents = new ConcurrentHashMap<>();
    /**
     * 鼠标监听器
     */
    private final Map<String, MouseEventHandler> mouseEvents = new ConcurrentHashMap<>();
    /**
     * 按键补偿事件时间轴
     */
    private Timeline timeline = new Timeline();
    /**
     * 屏幕最大刷新率
     */
    private int fps = 60;
    /**
     * 舞台
     */
    private Stage stage;

    private final Map<KeyCode, KeyEvent> keys = new ConcurrentHashMap<>();
    private Thread keyCompensationThread;

    private EventHandler<KeyEvent> keyEventHandler;
    private EventHandler<MouseEvent> mouseEventHandler;

    {

        keyCompensationThread = new Thread(() -> {
            while (true) {
                if (!keys.isEmpty()) {
                    keys.forEach(new BiConsumer<KeyCode, KeyEvent>() {
                        @Override
                        public void accept(KeyCode code, KeyEvent event) {
                            log.trace("按键补偿:{}", code);
                            Iterator<Map.Entry<String, KeyEventHandler>> iterator = keyEvents.entrySet().iterator();
                            while (iterator.hasNext()) {
                                Map.Entry<String, KeyEventHandler> entry = iterator.next();
                                KeyEventHandler handler = entry.getValue();
                                handler.keyPressed(event);
                            }
                        }
                    });
                }
                try {
                    Thread.sleep(1000 / fps);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        keyCompensationThread.setDaemon(true);
        keyCompensationThread.start();
        //键盘事件
        keyEventHandler = new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent e) {
                log.trace("按键事件:{}", e.getCode());
                Iterator<Map.Entry<String, KeyEventHandler>> iterator = keyEvents.entrySet().iterator();
                switch (e.getEventType().getName()) {
                    case "KEY_PRESSED":
                        while (iterator.hasNext()) {
                            Map.Entry<String, KeyEventHandler> entry = iterator.next();
                            KeyEventHandler handler = entry.getValue();
                            handler.keyPressed(e);
                        }
                        keys.put(e.getCode(), e);
                        break;
                    case "KEY_RELEASED":
                        while (iterator.hasNext()) {
                            Map.Entry<String, KeyEventHandler> entry = iterator.next();
                            KeyEventHandler handler = entry.getValue();
                            handler.keyReleased(e);
                            if (keys.containsKey(e.getCode())) {
                                keys.remove(e.getCode());
                                handler.keyClicked(new KeyEvent(e.getSource(), e.getTarget(), KeyEventHandler.KEY_CLICKED, e.getCharacter(), e.getText(), e.getCode(), e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown()));
                            }
                        }
                        return;
                    case "KEY_TYPED":
                        while (iterator.hasNext()) {
                            Map.Entry<String, KeyEventHandler> entry = iterator.next();
                            KeyEventHandler handler = entry.getValue();
                            handler.keyTyped(e);
                        }
                }

            }
        };

        //鼠标事件
        mouseEventHandler = e -> {
            MouseEvent event = null;
            Iterator<Map.Entry<String, MouseEventHandler>> iterator = this.mouseEvents.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, MouseEventHandler> entry = iterator.next();
                MouseEventHandler handler = entry.getValue();
                if (handler.getRect() == null || handler.getRect().isContains(e.getX(), e.getY())) {
                    switch (e.getEventType().getName()) {
                        case "MOUSE_PRESSED":
                            handler.mousePressed(e);
                            break;
                        case "MOUSE_RELEASED":
                            handler.mouseReleased(e);
                            break;
                        case "MOUSE_CLICKED":
                            handler.mouseClicked(e);
                            break;
                        case "MOUSE_MOVED":
                            handler.mouseMoved(e);
                            break;
                        case "MOUSE_DRAGGED":
                            handler.mouseDragged(e);
                            break;
                        case "MOUSE_ENTERED":
                            handler.mouseEntered(e);
                            break;
                        case "MOUSE_EXITED":
                            handler.mouseExited(e);
                            break;
                    }
                }
            }

        };
    }

    /**
     * 设置场景刷新率
     *
     * @param fps 刷新率
     */
    public void setFps(int fps) {
        if (fps > 0) {
            this.fps = fps;
        }
    }

    /**
     * 移除场景
     *
     * @param id 场景ID
     */
    public void removeScene(String id) {
        if (gameScenes.containsKey(id)) {
            //隐藏场景,用于触发生命周期,以及解绑与舞台的事件
            hideGameScene(id);
            //移除场景的历史记录
            GameScene scene = gameScenes.remove(id);
            //调用生命周期方法
            scene.destory(gameContext);
            //移除按键事件
            this.removeKeyListener(scene.getId());
            //移除鼠标事件
            this.removeKeyListener(scene.getId());
        }
    }

    /**
     * 隐藏游戏场景
     *
     * @param id 场景ID
     */
    public void hideGameScene(String id) {
        if (this.activeSceneId == id) {
            //只有场景处于显示状态才隐藏
            GameScene gameScene = gameScenes.get(id);
            gameScene.visible(getGameContext(), false);
            unbindEvent(gameScene);
            this.removeKeyListener(gameScene.getId());
            this.removeKeyListener(gameScene.getId());
            this.activeSceneId = null;
        }
    }

    /**
     * 显示游戏场景
     *
     * @param id 场景ID
     */
    public void showGameScene(String id) {
        Objects.requireNonNull(id, "场景ID不能为null");
        if (this.activeSceneId != null) {
            hideGameScene(this.activeSceneId);
        }
        if (gameScenes.containsKey(id)) {
            GameScene scene = gameScenes.get(id);
            //初始化事件
            bindEvent(scene);
            //键盘监听
            this.addKeyListener(scene.getId(), scene);
            //鼠标监听
            this.addMouseListener(scene.getId(), scene);
            scene.visible(getGameContext(), true);
            this.activeSceneId = id;
            this.stage.setScene(scene);
        } else {
            throw new RuntimeException("场景不存在: " + id);
        }

    }

    /**
     * 创建场景,场景大小自动匹配舞台大小
     *
     * @param sceneType 场景类型
     * @return 场景实例对象
     */
    public String createGameScene(Class<? extends GameScene> sceneType, String sceneId) {
        Constructor<? extends GameScene> constructor = ReflectUtil.getConstructor(sceneType, String.class, double.class, double.class);
        try {
            if (constructor != null) {
                constructor.setAccessible(true);
                GameScene t = null;
                t = constructor.newInstance(sceneId, getSize().getWidth(), getSize().getHeight());
                return t.getId();
            } else {
                throw new RuntimeException("场景必须提供GameScene(double,double)或者GameScene(String,double,double)构造");
            }
        } catch (Exception e) {
            throw ExceptionUtil.wrapRuntime(e);
        }
    }

    /**
     * 根据ID获取场景对象
     * @param sceneId 场景ID
     * @param <T> 场景类型
     * @return 场景对象
     */
    public <T extends GameScene> T getGameScene(String sceneId){
        if (this.gameScenes.containsKey(sceneId)){
            return (T) this.gameScenes.get(sceneId);
        }
        throw new NullPointerException("场景["+sceneId+"]不存在");
    }

    /**
     * 创建一个场景
     * @return 场景ID
     */
    public String createGameScene(){
        return this.createGameScene(GameScene.class);
    }

    /**
     * 创建场景,场景大小自动匹配舞台大小
     *
     * @param sceneType 场景类型
     * @return 场景实例对象
     */
    public String createGameScene(Class<? extends GameScene> sceneType) {
        try {
            Constructor<? extends GameScene> constructor = ReflectUtil.getConstructor(sceneType, double.class, double.class);
            GameScene t = null;
            if (constructor != null) {
                constructor.setAccessible(true);
                t = constructor.newInstance(getSize().getWidth(), getSize().getHeight());
            } else {
                constructor = ReflectUtil.getConstructor(sceneType, String.class, double.class, double.class);
                if (constructor != null) {
                    constructor.setAccessible(true);
                    t = constructor.newInstance(IdUtil.fastUUID(), getSize().getWidth(), getSize().getHeight());
                } else {
                    throw new RuntimeException("场景必须提供GameScene(double,double)或者GameScene(String,double,double)构造");
                }
            }
            addGameScene(t);
            return t.getId();
        } catch (Exception e) {
            throw ExceptionUtil.wrapRuntime(e);
        }
    }

    /**
     * 添加游戏场景到舞台,场景大小不会自动匹配舞台大小
     *
     * @param scene 游戏场景
     */
    public String addGameScene(GameScene scene) {
        Objects.requireNonNull(scene, "游戏场景不能为null");
        if (scene.getId() == null) {
            scene.setId(IdUtil.objectId());
        }
        if (!gameScenes.containsKey(scene.getId())) {
            scene.setStage(this.stage);
            //初始化场景
            scene.init(getGameContext());
            gameScenes.put(scene.getId(), scene);
        }
        return scene.getId();
    }

    /**
     * 解除事件绑定
     *
     * @param scene 场景
     */
    private void unbindEvent(Scene scene) {
        //监听键盘事件
        scene.setOnKeyPressed(null);
        scene.setOnKeyReleased(null);
        scene.setOnKeyTyped(null);
        //监听鼠标事件
        scene.setOnMousePressed(null);
        scene.setOnMouseReleased(null);
        scene.setOnMouseClicked(null);
        scene.setOnMouseMoved(null);
        scene.setOnMouseDragged(null);
        scene.setOnMouseEntered(null);
        scene.setOnMouseExited(null);
    }

    /**
     * 绑定事件
     *
     * @param scene 场景
     */
    public void bindEvent(Scene scene) {
        //监听键盘事件
        scene.setOnKeyPressed(keyEventHandler);
        scene.setOnKeyReleased(keyEventHandler);
        scene.setOnKeyTyped(keyEventHandler);
        //监听鼠标事件
        scene.setOnMousePressed(mouseEventHandler);
        scene.setOnMouseReleased(mouseEventHandler);
        scene.setOnMouseClicked(mouseEventHandler);
        scene.setOnMouseMoved(mouseEventHandler);
        scene.setOnMouseDragged(mouseEventHandler);
        scene.setOnMouseEntered(mouseEventHandler);
        scene.setOnMouseExited(mouseEventHandler);
    }

    /**
     * 移除指定ID的事件监听器
     *
     * @param id 事件ID
     * @return 监听器对象
     */
    public KeyEventHandler removeKeyListener(String id) {
        return this.keyEvents.remove(id);
    }

    /**
     * 移除指定ID的事件监听器
     *
     * @param id 事件ID
     * @return 监听器对象
     */
    public MouseEventHandler removeMouseListener(String id) {
        return this.mouseEvents.remove(id);
    }

    /**
     * 添加键盘事件
     *
     * @param handler 事件处理器
     */
    public void addKeyListener(String id, KeyEventHandler handler) {
        this.keyEvents.put(id, handler);
    }

    /**
     * 添加鼠标事件
     *
     * @param handler 事件处理器
     */
    public void addMouseListener(String id, MouseEventHandler handler) {
        this.mouseEvents.put(id, handler);
    }

    public double getX() {
        return this.stage.getX();
    }

    public double getY() {
        return this.stage.getY();
    }

    public void moveTo(double x, double y) {
        this.stage.setX(x);
        this.stage.setY(y);
    }

    public void move(double x, double y) {
        this.moveTo(getX() + x, getY() + y);
    }

    public void initDebug() {
        DebugHelper.setBorder(false);
    }

    @Override
    public void start(Stage stage) throws Exception {
        initDebug();
        this.stage = stage;
        this.gameContext.<Dimension>addListener(SIZE, new ChangeListener<Dimension>() {
            @Override
            public void changed(ObservableValue<? extends Dimension> observable, Dimension oldValue, Dimension newValue) {
                stage.setWidth(newValue.getWidth());
                stage.setHeight(newValue.getHeight());
            }
        });
        stage.initStyle(StageStyle.UNDECORATED);
        this.init(gameContext);
        stage.show();
    }

    @Override
    public void stop() throws Exception {
        timeline.stop();
        this.destory(gameContext);
        super.stop();
    }

    @Override
    public GameContext<JameApplication> getGameContext() {
        return gameContext;
    }

    public void setSize(Dimension size) {
        if (size != null) {
            this.setSize(size.getWidth(), size.getHeight());
        }
    }

    public void setSize(double width, double height) {
        this.size.setDimension(width, height);
        this.gameContext.put(SIZE, this.getSize());
    }

}
